package com.hzy.utils;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 压缩文件和目录的工具类。
 * 提供将单个文件或整个目录压缩为ZIP归档的功能。
 * 此版本已改造为使用Vert.x的executeBlocking来处理阻塞式IO操作，
 * 以避免阻塞Vert.x事件循环。toZip方法现在返回一个Future，实现完全非阻塞的公共接口。
 */
public class ZipUtils {
    private static final Logger logger = LoggerFactory.getLogger(ZipUtils.class);

    private static final int BUFFER_SIZE = 2 * 1024; // 2KB 缓冲区大小

    /**
     * 将指定源目录或文件异步压缩成ZIP文件。
     * 此方法将整个压缩操作提交到Vert.x的worker线程池中执行，以避免阻塞事件循环。
     * 调用此方法将立即返回一个Future，您可以通过监听此Future来获取操作结果（成功或失败）。
     *
     * @param vertx            Vert.x 实例，用于执行阻塞操作。
     * @param srcDir           要压缩的源目录或文件的路径。
     * @param out              ZIP归档将写入的输出流。
     * @param KeepDirStructure 是否保留原来的目录结构，true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,导致压缩失败)。
     * @return 一个 Future<Void>，表示压缩操作的异步结果。如果压缩成功，Future 将成功完成；
     * 如果压缩失败，Future 将失败并携带 RuntimeException。
     */
    public static Future<Void> toZip(Vertx vertx, String srcDir, OutputStream out, boolean KeepDirStructure) {
        return vertx.executeBlocking(promise -> {
            ZipOutputStream zos = null;
            try {
                zos = new ZipOutputStream(out);
                File sourceFile = new File(srcDir);
                // 执行实际的压缩逻辑
                compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
                promise.complete(); // 成功完成 Promise
            } catch (IOException e) {
                logger.error("ZIP压缩操作失败: {}", e.getMessage(), e);
                promise.fail(new RuntimeException("ZIP压缩操作失败", e)); // 失败 Promise
            } finally {
                if (zos != null) {
                    try {
                        zos.close();
                    } catch (IOException e) {
                        // 记录关闭流的错误。这里不重新promise.fail()，因为可能原始操作已经成功或失败。
                        logger.error("关闭ZipOutputStream失败: {}", e.getMessage(), e);
                    }
                }
            }
        }, false); // false 表示Vert.x将选择一个worker线程
    }

    /**
     * 递归压缩文件或目录。
     * 此方法在阻塞线程中执行，不直接使用Vert.x的异步IO，因为它被包含在executeBlocking调用中。
     *
     * @param sourceFile       要压缩的源文件或目录。
     * @param zos              ZIP输出流。
     * @param name             在ZIP文件中对应Entry的名称（路径）。
     * @param KeepDirStructure 是否保留原来的目录结构。
     * @throws IOException IO操作异常。
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure) throws IOException {
        byte[] buf = new byte[BUFFER_SIZE];
        if (sourceFile.isFile()) {
            // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = null;
            try {
                in = new FileInputStream(sourceFile);
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        logger.error("关闭FileInputStream失败: {}", e.getMessage(), e);
                    }
                }
            }
            // 完成当前ZIP条目
            zos.closeEntry();
        } else {
            File[] listFiles = sourceFile.listFiles();
            if (listFiles == null || listFiles.length == 0) {
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if (KeepDirStructure) {
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }
            } else {
                for (File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
                        // 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(), KeepDirStructure);
                    }
                }
            }
        }
    }
}